﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace SpotifyTechPuzzles.BestBefore
{
    /// <summary>
    /// Spec: Hg$\Docs\Best Before Puzzle - Spotify.htm
    /// </summary>
    public class MinDateCalculator
    {
        private readonly string dateString;
        private const string VALUE_FORMAT = "yyyy-MM-dd";
        private const string INVALID_INPUT_MSG = "is illegal";

        public MinDateCalculator(string dateString)
        {
            // no validation; assuming input according to spec: [int]/[int]/[int]
            this.dateString = dateString;
        }

        public string Calculate()
        {
            var dateStringWithTwoCharsParts = GetDateStringWithTwoCharsParts();
            var dates = GetDatesFrom(dateStringWithTwoCharsParts);

            if (dates.AllAreNull())
                return "{0} {1}".FormatWith(dateString, INVALID_INPUT_MSG);

            var minDate = dates.Min().Value;
            minDate = AdjustCentury(minDate);

            return minDate.ToString(VALUE_FORMAT);
        }

        private string GetDateStringWithTwoCharsParts()
        {
            return dateString
                .Split('/')
                .Select(AsTwoChars)
                .JoinAsString("-");
        }

        private static DateTime AdjustCentury(DateTime minDate)
        {
            return minDate.Year < 2000 ? minDate.AddYears(100) : minDate;
        }

        private static IEnumerable<DateTime?> GetDatesFrom(string dateStringWithTwoCharsParts)
        {
            // only three date formats, YMD/DMY/MDY, according to: http://en.wikipedia.org/wiki/Date_format_by_country
            const string YMD_FORMAT = "yy-MM-dd";
            const string MDY_FORMAT = "MM-dd-yy";
            const string DMY_FORMAT = "dd-MM-yy";
            
            return new[] { YMD_FORMAT, MDY_FORMAT, DMY_FORMAT }
                .Select(f => dateStringWithTwoCharsParts.ParseAsDate(f));
        }

        private static string AsTwoChars(string arg)
        {
            if (arg.Length == 2)
                return arg;

            if (arg.Length == 1)
                return arg.PadLeft(2, '0');

            return new string(arg.Reverse().Take(2).Reverse().ToArray());
        }
    }

    internal static class MinDateCalculatorExtensions
    {
        internal static string JoinAsString(this IEnumerable<string> @this, string separator)
        {
            return String.Join(separator, @this);
        }

        internal static DateTime? ParseAsDate(this string @this, string format)
        {
            DateTime date;
            if (!DateTime.TryParseExact(@this, format, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
                return null;

            return date;
        }

        internal static string FormatWith(this string @this, params object[] args)
        {
            return string.Format(CultureInfo.InvariantCulture, @this, args);
        }

        internal static bool AllAreNull(this IEnumerable<DateTime?> @this)
        {
            return @this.All(d => !d.HasValue);
        } 
    }
}